/**
 * Computer Graphics Lecture WS 2009/2010 Ulm University
 * Creator: Manuel Finckh
 * Email:   manuel.finckh@uni-ulm.de
 */

#ifndef VEC_H
#define VEC_H

#include <cmath>
#include <cfloat>
#include <stdio.h>

struct Vec3 {
  float x, y, z;

  Vec3();
  Vec3(const float a);
  Vec3(const float * const a);
  Vec3(const float a, const float b, const float c);
  
  Vec3 operator - () const;
  Vec3 operator + (const Vec3 &v) const;
  Vec3& operator += (const Vec3 &v);
  Vec3 operator - (const Vec3 &v) const;
  Vec3& operator -= (const Vec3 &v);
  Vec3 operator * (const float a) const;
  Vec3& operator *= (const float a);
  friend Vec3 operator * (const float, const Vec3 &v);

  Vec3 operator / (const float a) const;
  Vec3& operator /= (const float a);

  float operator * (const Vec3 &v) const; // dot product
  
  Vec3 operator / (const Vec3 &v) const;
  
  float& operator[] (const int i);
  float operator[](const int i) const;
 
  float length_sqr() const;
  float length() const;
  float normalize();
  void printVec3();

  void abs();

  void minf(const Vec3 &v);
  void maxf(const Vec3 &v);

  int maxIndex();

  static Vec3 cross(const Vec3 &v, const Vec3 &w);
  static Vec3 product(const Vec3 &v, const Vec3 &w);

  static void onb(Vec3 &u, Vec3 &v, Vec3 &w, const Vec3 &n);
};

inline Vec3::Vec3() { }

inline Vec3::Vec3(const float a) : x(a), y(a), z(a) { }

inline Vec3::Vec3(const float * const a) {
  x = a[0];
  y = a[1];
  z = a[2];
}

inline Vec3::Vec3(const float a, const float b, const float c) : x(a), y(b), z(c) { }

inline void Vec3::printVec3(){
 printf("[%f,  %f,  %f]\n",x,y,z);
}

inline Vec3 Vec3::operator - () const {
  return Vec3( -x, -y, -z);
}

inline Vec3 Vec3::operator + (const Vec3 &v) const {
  return Vec3(x + v.x, y + v.y, z + v.z);
}

inline Vec3& Vec3::operator += (const Vec3 &v) {
 x += v.x;
 y += v.y;
 z += v.z;

 return *this;
}

inline Vec3 Vec3::operator - (const Vec3 &v) const {
  return Vec3(x - v.x, y - v.y, z - v.z);
}

inline Vec3& Vec3::operator -= (const Vec3 &v) {
 x -= v.x;
 y -= v.y;
 z -= v.z;

 return *this;
}

inline Vec3 Vec3::operator * (const float a) const {
  return Vec3(x * a, y * a, z * a);
}

inline Vec3& Vec3::operator *= (const float a) {
 x *= a;
 y *= a;
 z *= a;

 return *this;
}

inline Vec3 operator * (const float a, const Vec3 &v) {
  return v * a;
}

inline float Vec3::operator * (const Vec3 &v) const {
  return (x * v.x + y * v.y + z * v.z);
}

inline Vec3 Vec3::operator / (const Vec3 &v) const {
  return Vec3(x / v.x, y / v.y, z / v.z);
}

inline Vec3 Vec3::operator / (const float a) const {
  const float f = 1.0f / a;
  return Vec3(x * f, y * f, z * f);
}

inline Vec3& Vec3::operator /= (const float a) {
  *this *= 1.0f / a;
  
  return *this;
}

inline float& Vec3::operator[] (const int i) {
  return (&x)[i];
}

inline float Vec3::operator[] (const int i) const {
  return (&x)[i];
}

inline float Vec3::length_sqr() const {
  return (*this) * (*this);
}

inline float Vec3::length() const {
  return sqrtf( (*this) * (*this) );
}

inline float Vec3::normalize() {
  float l = this->length();
  *this /= l;

  return l;
}

inline void Vec3::abs() {
  x = x < 0.0f ? -x : x;
  y = y < 0.0f ? -y : y;
  z = z < 0.0f ? -z : z;
}

inline void Vec3::minf(const Vec3 &v) {
  x = x < v.x ? x : v.x;
  y = y < v.y ? y : v.y;
  z = z < v.z ? z : v.z;
}

inline void Vec3::maxf(const Vec3 &v) {
  x = x > v.x ? x : v.x;
  y = y > v.y ? y : v.y;
  z = z > v.z ? z : v.z;
}

inline int Vec3::maxIndex() {
  if ( x > y ) {
    if ( x > z ) return 0;
    else return 2;
  }
  else if ( z > y ) return 2;
  else return 1;
}

inline Vec3 Vec3::cross(const Vec3 &v, const Vec3 &w) {
  Vec3 u;
  u.x = v.y * w.z - v.z * w.y;
  u.y = v.z * w.x - v.x * w.z;
  u.z = v.x * w.y - v.y * w.x;
  
  return u;
}

inline Vec3 Vec3::product(const Vec3 &v, const Vec3 &w) {
  Vec3 u;
  u.x = v.x * w.x;
  u.y = v.y * w.y;
  u.z = v.z * w.z;
  
  return u;
}

inline void Vec3::onb(Vec3 &u, Vec3 &v, Vec3 &w, const Vec3 &n) {
  const Vec3 m0(1.0f, 0.0f, 0.0f);
  const Vec3 m1(0.0f, 1.0f, 0.0f);

  w = n;
  w.normalize();

  u = Vec3::cross(w, m0);
  if (u.length_sqr() < 0.1f) u = Vec3::cross(w, m1);
  u.normalize();
  v = Vec3::cross(w, u);
  // w, u normalized and orthogonal -> v normalized
}

struct Vec2 {
  float x, y;

  Vec2();
  Vec2(const float a);
  Vec2(const float a, const float b);
  
  Vec2 operator - () const;
  Vec2 operator + (const Vec2 &v) const;
  Vec2& operator += (const Vec2 &v);
  Vec2 operator - (const Vec2 &v) const;
  Vec2& operator -= (const Vec2 &v);
  Vec2 operator * (const float a) const;
  Vec2& operator *= (const float a);
  friend Vec2 operator * (const float, const Vec2 &v);

  Vec2 operator / (const float a) const;
  Vec2& operator /= (const float a);

  float operator * (const Vec2 &v) const; // dot product
  
  float& operator[] (const int i);
  float operator[](const int i) const;
 
  float length_sqr() const;
  float length() const;
  float normalize();

  void minf(const Vec2 &v);
  void maxf(const Vec2 &v);
};

inline Vec2::Vec2() { }
inline Vec2::Vec2(const float a) : x(a), y(a) { }
inline Vec2::Vec2(const float a, const float b) : x(a), y(b) { }


inline Vec2 Vec2::operator - () const {
  return Vec2( -x, -y);
}

inline Vec2 Vec2::operator + (const Vec2 &v) const {
  return Vec2(x + v.x, y + v.y);
}

inline Vec2& Vec2::operator += (const Vec2 &v) {
 x += v.x;
 y += v.y;

 return *this;
}

inline Vec2 Vec2::operator - (const Vec2 &v) const {
  return Vec2(x - v.x, y - v.y);
}

inline Vec2& Vec2::operator -= (const Vec2 &v) {
 x -= v.x;
 y -= v.y;

 return *this;
}

inline Vec2 Vec2::operator * (const float a) const {
  return Vec2(x * a, y * a);
}

inline Vec2& Vec2::operator *= (const float a) {
 x *= a;
 y *= a;

 return *this;
}

inline Vec2 operator * (const float a, const Vec2 &v) {
  return v * a;
}

inline float Vec2::operator * (const Vec2 &v) const {
  return (x * v.x + y * v.y);
}

inline Vec2 Vec2::operator / (const float a) const {
  const float f = 1.0f / a;
  return Vec2(x * f, y * f);
}

inline Vec2& Vec2::operator /= (const float a) {
  *this *= 1.0f / a;
  
  return *this;
}

inline float& Vec2::operator[] (const int i) {
  return (&x)[i];
}

inline float Vec2::operator[] (const int i) const {
  return (&x)[i];
}

inline float Vec2::length_sqr() const {
  return (*this) * (*this);
}

inline float Vec2::length() const {
  return sqrtf( (*this) * (*this) );
}

inline float Vec2::normalize() {
  float l = this->length();
  *this /= l;

  return l;
}

inline void Vec2::minf(const Vec2 &v) {
  x = x < v.x ? x : v.x;
  y = y < v.y ? y : v.y;
}

inline void Vec2::maxf(const Vec2 &v) {
  x = x > v.x ? x : v.x;
  y = y > v.y ? y : v.y;
}

#endif

